/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.fos.obs;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Proxy.Type;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.jianggujin.fos.JFOSLazyException;
import com.jianggujin.fos.JSecurityToken;
import com.jianggujin.fos.JSecurityTokenService;
import com.jianggujin.fos.obs.JOBSConfiguration.JOBSSessionTokenConfiguration;
import com.obs.services.internal.Constants.CommonHeaders;

import lombok.NonNull;
import okhttp3.Authenticator;
import okhttp3.Call;
import okhttp3.Credentials;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.Route;

/**
 * @author jianggujin
 *
 */
public class JOBSSecurityTokenService implements JSecurityTokenService {
    private JOBSConfiguration configuration;
    private Request tokenRequest = null;
    private Request securityRequest = null;
    private OkHttpClient httpClient = null;

    public JOBSSecurityTokenService(@NonNull JOBSConfiguration configuration) {
        this.configuration = configuration;
        JOBSSessionTokenConfiguration sessionTokenConfiguration = configuration.getSessionTokenConfiguration();
        try {
            initHttpClient(sessionTokenConfiguration);
            initTokenRequest(sessionTokenConfiguration);
            initSecurityRequest(sessionTokenConfiguration);
        } catch (Exception e) {
            throw new JFOSLazyException(e.getMessage(), e);
        }
    }

    private void initHttpClient(JOBSSessionTokenConfiguration sessionTokenConfiguration) throws Exception {
        X509TrustManager xtm = new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                X509Certificate[] x509Certificates = new X509Certificate[0];
                return x509Certificates;
            }
        };

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, new TrustManager[] { xtm }, new SecureRandom());

        HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
            @Override
            public boolean verify(String arg0, SSLSession arg1) {
                return true;
            }
        };

        @SuppressWarnings("deprecation")
        OkHttpClient.Builder builder = new OkHttpClient.Builder().followRedirects(false).retryOnConnectionFailure(false)
                .sslSocketFactory(sslContext.getSocketFactory()).hostnameVerifier(DO_NOT_VERIFY).cache(null);

        if (sessionTokenConfiguration.isProxyIsable()) {
            builder.proxy(new java.net.Proxy(Type.HTTP, new InetSocketAddress(
                    sessionTokenConfiguration.getProxyHostAddress(), sessionTokenConfiguration.getProxyPort())));

            Authenticator proxyAuthenticator = new Authenticator() {
                @Override
                public Request authenticate(Route route, Response response) throws IOException {
                    String credential = Credentials.basic(sessionTokenConfiguration.getProxyUser(),
                            sessionTokenConfiguration.getProxyPassword());
                    return response.request().newBuilder().header(CommonHeaders.PROXY_AUTHORIZATION, credential)
                            .build();
                }
            };
            builder.proxyAuthenticator(proxyAuthenticator);
        }

        this.httpClient = builder.build();
    }

    private void initTokenRequest(JOBSSessionTokenConfiguration sessionTokenConfiguration) throws Exception {
        StringBuilder tokenBuilder = new StringBuilder(256)
                .append("{\"auth\": {\"identity\": {\"methods\": [\"password\"],\"password\": {\"user\": {\"name\": \"")
                .append(sessionTokenConfiguration.getUserName()).append("\",\"password\": \"")
                .append(sessionTokenConfiguration.getPassword() + "\",\"domain\": {\"name\": \"")
                .append(sessionTokenConfiguration.getDomainName())
                .append("\"}}}},\"scope\": {\"domain\": {\"name\": \"")
                .append(sessionTokenConfiguration.getDomainName()).append("\"}}}}");
        this.tokenRequest = new Request.Builder().addHeader("Content-Type", "application/json;charset=utf8")
                .url(configuration.getEndPoint() + "/v3/auth/tokens")
                .post(createRequestBody("application/json", tokenBuilder.toString())).build();
    }

    private void initSecurityRequest(JOBSSessionTokenConfiguration sessionTokenConfiguration) throws Exception {
        StringBuilder securityBuilder = new StringBuilder(256)
                .append("{\"auth\": {\"identity\": {\"methods\": [\"token\"],\"token\": {\"id\": \"").append(getToken())
                .append("\",\"duration-seconds\": \"").append(sessionTokenConfiguration.getDurationSeconds())
                .append("\"}}}}");
        this.securityRequest = new Request.Builder().addHeader("Content-Type", "application/json;charset=utf8")
                .url(configuration.getEndPoint() + "/v3.0/OS-CREDENTIAL/securitytokens")
                .post(createRequestBody("application/json", securityBuilder.toString())).build();
    }

    private String getToken() throws IOException {
        Call c = this.httpClient.newCall(this.tokenRequest);
        try (Response res = c.execute()) {
            return res.header("X-Subject-Token").toString();
        }
    }

    @Override
    public JSecurityToken getSecurityToken() {
        Call c = this.httpClient.newCall(this.securityRequest);
        try (Response res = c.execute()) {
            if (res.body() != null) {
                String content = res.body().string();
                if (content == null || content.trim().equals("")) {
                    System.out.println("\n");
                } else {
                    System.out.println("Content:" + content + "\n\n");
                }
            } else {
                System.out.println("\n");
            }
        } catch (Exception e) {
            throw new JFOSLazyException(e.getMessage(), e);
        }
        return null;
    }

    private static RequestBody createRequestBody(String mimeType, String content) throws UnsupportedEncodingException {
        return RequestBody.create(MediaType.parse(mimeType), content.getBytes("UTF-8"));
    }

    @Override
    public void destory() {
    }
}
